import api from './api'
import 'reset-css'
import 'Assets/style/common.scss'
import 'Assets/script/polyfill'
import io from 'socket.io-client'

document.addEventListener('DOMContentLoaded', function () {

  const dom = {
    wrapper: document.getElementById('wrapper'),
    player1: document.querySelector('.player1'),
    player2: document.querySelector('.player2'),
    over: document.querySelectorAll('.over'),
  }
  dom.wait = dom.player1.querySelector('.wait')
  dom.myCvs = dom.player1.querySelector('.stage')
  dom.myScore = dom.player1.querySelector('.score>i')
  dom.myEfficiency = dom.player1.querySelector('.efficiency>i')
  dom.hisCvs = dom.player2.querySelector('.stage')
  dom.hisScore = dom.player2.querySelector('.score>i')
  dom.Efficiency = dom.player2.querySelector('.efficiency>i')

  const myCtx = dom.myCvs.getContext('2d')
  const hisCtx = dom.hisCvs.getContext('2d')
  const STAGE_WIDTH = 350
  const STAGE_HEIGHT = 350

  let gameSocket = null

  const KEYS = {
    SHIFT: 16,
    SPACE: 32,
    UP: 38,
    DOWN: 40,
    LEFT: 37,
    RIGHT: 39,
    W: 87,
    A: 65,
    S: 83,
    D: 68,
  }
  const DIRS = {
    NONE: 0,
    NORTH: 1,
    EAST: 2,
    SOUTH: 4,
    WEST: 8,
    NORTH_EAST: 3,
    NORTH_WEST: 9,
    SOUTH_EAST: 6,
    SOUTH_WEST: 12,
  }

  const allowedDirections = {}
  allowedDirections[DIRS.NORTH] = [DIRS.EAST, DIRS.NORTH_EAST, DIRS.NORTH_WEST, DIRS.WEST]
  allowedDirections[DIRS.EAST] = [DIRS.SOUTH, DIRS.SOUTH_EAST, DIRS.NORTH_EAST, DIRS.NORTH]
  allowedDirections[DIRS.SOUTH] = [DIRS.EAST, DIRS.SOUTH_EAST, DIRS.SOUTH_WEST, DIRS.WEST]
  allowedDirections[DIRS.WEST] = [DIRS.SOUTH, DIRS.SOUTH_WEST, DIRS.NORTH_WEST, DIRS.NORTH]
  allowedDirections[DIRS.NORTH_EAST] = [DIRS.NORTH, DIRS.EAST]
  allowedDirections[DIRS.NORTH_WEST] = [DIRS.NORTH, DIRS.WEST]
  allowedDirections[DIRS.SOUTH_EAST] = [DIRS.SOUTH, DIRS.EAST]
  allowedDirections[DIRS.SOUTH_WEST] = [DIRS.SOUTH, DIRS.WEST]

  const SQRT_2 = Math.sqrt(2)
  let score = 0

  let vectors = [DIRS.NONE, DIRS.NONE]
  window.addEventListener('keydown', function (e) {
    if (!mySnake || mySnake.over) return false
    let direction = DIRS.NONE
    switch (e.keyCode) {
      case KEYS.W:
      case KEYS.UP:
        direction = DIRS.NORTH
        break
      case KEYS.D:
      case KEYS.RIGHT:
        direction = DIRS.EAST
        break
      case KEYS.S:
      case KEYS.DOWN:
        direction = DIRS.SOUTH
        break
      case KEYS.A:
      case KEYS.LEFT:
        direction = DIRS.WEST
        break
    }
    if (vectors.indexOf(direction) === -1) {
      vectors[0] === DIRS.NONE ? vectors[0] = direction : vectors[1] = direction
    }
    if ((vectors[0] | vectors[1]) !== mySnake.direction) {
      const myTurn = vectors[0] | vectors[1]
      if (mode === 'multi' && gameSocket) {
        gameSocket.emit('my turn', myTurn)
      }
      mySnake.turn(myTurn)
    }
  })
  let oldDirection = DIRS.NONE
  window.addEventListener('keyup', function (e) {
    if (!mySnake || mySnake.over) return false
    let direction = DIRS.NONE
    switch (e.keyCode) {
      case KEYS.W:
      case KEYS.UP:
        direction = DIRS.NORTH
        break
      case KEYS.S:
      case KEYS.DOWN:
        direction = DIRS.SOUTH
        break
      case KEYS.A:
      case KEYS.LEFT:
        direction = DIRS.WEST
        break
      case KEYS.D:
      case KEYS.RIGHT:
        direction = DIRS.EAST
        break
    }

    oldDirection = vectors[vectors.indexOf(direction)]
    vectors[vectors.indexOf(direction)] = DIRS.NONE
    if (vectors[0] === DIRS.NONE && vectors[1] === DIRS.NONE) { // 必须有一个方向
      vectors[vectors.indexOf(direction)] = oldDirection
    } else {
      if ((vectors[0] | vectors[1]) !== mySnake.direction) {
        mySnake.turn(vectors[0] | vectors[1])
      }
    }
  })

  class Food {
    constructor (ctx) {
      this.ctx = ctx
      this.x = 0
      this.y = 0
      this.color = '#f850c2'
      this.minRadius = 3
      this.radius = 6
      this.foodPosList = []
      this.createFoodPosList()
    }

    // 从服务器创建
    createFoodPosList () {
      for (let i = 0; i < 100; i++) {
        this.foodPosList.push({
          x: Math.random() * (STAGE_WIDTH - 20) + 10,
          y: Math.random() * (STAGE_HEIGHT - 20) + 10,
        })
      }
    }

    // 从服务器获取
    getPos () {
      return this.foodPosList[score]
    }

    update () {
      this.radius = Math.max(this.radius / (1 + Math.floor(score / 10) * 0.1), this.minRadius)
      const pos = this.getPos()
      this.x = pos.x
      this.y = pos.y
    }

    render () {
      this.ctx.save()
      this.ctx.beginPath()
      this.ctx.fillStyle = this.color
      this.ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true)
      this.ctx.fill()
      this.ctx.restore()
    }
  }

  class Snake {

    constructor ({ctx, x, y, length, width, color, speed}) {
      this.ctx = ctx
      this.x = x
      this.y = y
      this.bodyLength = length
      this.bodyWidth = width
      this.maxSpeed = 7
      this.speed = 1
      this.direction = DIRS.NORTH
      this.points = []
      this.SQRT_HEAD = width / 2 / SQRT_2
      this.over = false
      this.food = new Food(ctx)
    }

    eat () {
      score += 1
      this.bodyLength += 10
      this.speed = Math.min(this.speed * (1 + Math.floor(score / 10) * 0.03), this.maxSpeed)
      this.food.update()
      updateDOM()
    }

    turn (direction) {
      if (direction !== DIRS.NONE) {
        if (allowedDirections[this.direction].indexOf(direction) !== -1) {
          this.points.push({
            x: this.x,
            y: this.y,
            direction: this.direction
          })
          this.direction = direction
          return true
        }
      }
      return false
    }

    update () {
      switch (this.direction) {
        case DIRS.NORTH:
          this.y -= this.speed
          break
        case DIRS.EAST:
          this.x += this.speed
          break
        case DIRS.SOUTH:
          this.y += this.speed
          break
        case DIRS.WEST:
          this.x -= this.speed
          break
        case DIRS.NORTH_EAST:
          this.x += this.speed / SQRT_2
          this.y -= this.speed / SQRT_2
          break
        case DIRS.NORTH_WEST:
          this.x -= this.speed / SQRT_2
          this.y -= this.speed / SQRT_2
          break
        case DIRS.SOUTH_EAST:
          this.x += this.speed / SQRT_2
          this.y += this.speed / SQRT_2
          break
        case DIRS.SOUTH_WEST:
          this.x -= this.speed / SQRT_2
          this.y += this.speed / SQRT_2
          break
      }

      if (Math.sqrt(Math.pow(this.x - this.food.x, 2) + Math.pow(this.y - this.food.y, 2)) <= this.bodyWidth / 2 + this.food.radius) {
        this.eat()
      }
      this.render()
    }

    render () {

      this.ctx.save()

      this.ctx.fillStyle = 'black'

      // const gradient = stageCtx.createLinearGradient(0, 200, 0, 548)
      // gradient.addColorStop(0, '#000')
      // gradient.addColorStop(1, '#888')
      // ctx0b.fillStyle = gradient

      this.ctx.strokeStyle = 'gray'
      this.ctx.lineWidth = this.bodyWidth
      // stageCtx.lineCap = 'round'
      this.ctx.lineJoin = 'round'

      // head
      this.ctx.beginPath()
      this.ctx.arc(this.x, this.y, this.bodyWidth / 2, 0, Math.PI * 2, true)
      this.ctx.fill()
      let headTopPoint = []
      switch (this.direction) {
        case DIRS.NORTH:
          headTopPoint = [this.x, this.y - this.bodyWidth / 2]
          break
        case DIRS.EAST:
          headTopPoint = [this.x + this.bodyWidth / 2, this.y]
          break
        case DIRS.SOUTH:
          headTopPoint = [this.x, this.y + this.bodyWidth / 2]
          break
        case DIRS.WEST:
          headTopPoint = [this.x - this.bodyWidth / 2, this.y]
          break
        case DIRS.NORTH_EAST:
          headTopPoint = [this.x + this.SQRT_HEAD, this.y - this.SQRT_HEAD]
          break
        case DIRS.NORTH_WEST:
          headTopPoint = [this.x - this.SQRT_HEAD, this.y - this.SQRT_HEAD]
          break
        case DIRS.SOUTH_EAST:
          headTopPoint = [this.x + this.SQRT_HEAD, this.y + this.SQRT_HEAD]
          break
        case DIRS.SOUTH_WEST:
          headTopPoint = [this.x - this.SQRT_HEAD, this.y + this.SQRT_HEAD]
          break
      }

      const bodyPoints = []
      let addedLength = 0
      if (this.points.length > 0) {
        addedLength += Math.sqrt(Math.pow(this.x - this.points[this.points.length - 1].x, 2) + Math.pow(this.y - this.points[this.points.length - 1].y, 2))
        let lastAddedLength = addedLength
        if (addedLength < this.bodyLength) {
          bodyPoints.push([this.points[this.points.length - 1].x, this.points[this.points.length - 1].y])
          if (this.points.length > 1) {
            for (let i = this.points.length - 1; i > 0; i--) {
              addedLength += Math.sqrt(Math.pow(this.points[i].x - this.points[i - 1].x, 2) + Math.pow(this.points[i].y - this.points[i - 1].y, 2))
              if (addedLength < this.bodyLength) {
                bodyPoints.push([this.points[i - 1].x, this.points[i - 1].y])
              }
              else {
                addedLength = lastAddedLength
                this.points = this.points.slice(i)
                break
              }
              lastAddedLength = addedLength
            }
          }
        }
        else {
          addedLength = 0
          this.points = []
        }
      }

      const leftLength = this.bodyLength - addedLength
      const lastPoint = this.points.length > 0 ? this.points[0] : {
        x: this.x,
        y: this.y,
        direction: this.direction,
      }
      let tailPoint = lastPoint
      switch (lastPoint.direction) {
        case DIRS.NORTH:
          tailPoint = {x: lastPoint.x, y: lastPoint.y + leftLength}
          break
        case DIRS.EAST:
          tailPoint = {x: lastPoint.x - leftLength, y: lastPoint.y}
          break
        case DIRS.SOUTH:
          tailPoint = {x: lastPoint.x, y: lastPoint.y - leftLength}
          break
        case DIRS.WEST:
          tailPoint = {x: lastPoint.x + leftLength, y: lastPoint.y}
          break
        case DIRS.NORTH_EAST:
          tailPoint = {x: lastPoint.x - leftLength / SQRT_2, y: lastPoint.y + leftLength / SQRT_2}
          break
        case DIRS.SOUTH_EAST:
          tailPoint = {x: lastPoint.x - leftLength / SQRT_2, y: lastPoint.y - leftLength / SQRT_2}
          break
        case DIRS.SOUTH_WEST:
          tailPoint = {x: lastPoint.x + leftLength / SQRT_2, y: lastPoint.y - leftLength / SQRT_2}
          break
        case DIRS.NORTH_WEST:
          tailPoint = {x: lastPoint.x + leftLength / SQRT_2, y: lastPoint.y + leftLength / SQRT_2}
          break
      }
      bodyPoints.push([tailPoint.x, tailPoint.y])

      // tail
      this.ctx.beginPath()
      this.ctx.fillStyle = 'gray'
      this.ctx.arc(tailPoint.x, tailPoint.y, this.bodyWidth / 2, 0, Math.PI * 2, true)
      this.ctx.fill()

      // body
      this.ctx.beginPath()
      this.ctx.moveTo(this.x, this.y)
      for (let i = 0; i < bodyPoints.length; i++) {
        this.ctx.lineTo(bodyPoints[i][0], bodyPoints[i][1])
      }
      this.ctx.stroke()

      if (this.x - this.bodyWidth / 2 <= 0 || this.y - this.bodyWidth / 2 <= 0 || this.x + this.bodyWidth / 2 >= STAGE_WIDTH || this.y + this.bodyWidth / 2 >= STAGE_HEIGHT || this.ctx.isPointInStroke(...headTopPoint)) {
        this.over = true
      }

      this.ctx.restore()

      this.food.render()

    }
  }

  let hisSnake = null
  let mySnake = null

  let fps = 60 // 帧率
  let interval = 1000 / fps
  let now = null
  let then = null
  let delta

  function animate () {
    now = Date.now()
    delta = now - then
    if (delta > interval) {
      then = now - (delta % interval)
      if (mySnake.over) {
        window.clearInterval(myEfficiencyTimer)
        dom.player1.classList.add('over')
        return false
      } else {
        myCtx.clearRect(0, 0, STAGE_WIDTH, STAGE_HEIGHT)
        mySnake.update()
      }

      if (mode === 'multi' && gameSocket) {
        if (hisSnake.over) {
          dom.player2.classList.add('over')
        } else {
          hisCtx.clearRect(0, 0, STAGE_WIDTH, STAGE_HEIGHT)
          hisSnake.update()
        }
      }

    }
    window.requestAnimationFrame(animate)
  }

  function updateDOM () {
    const value = score / ((Date.now() - startTime) / 1000 / 60)
    if (value < 10) {
      dom.myEfficiency.classList.remove('rock')
    } else {
      dom.myEfficiency.classList.add('rock')
    }
    dom.myEfficiency.innerHTML = Number(value).toFixed(1)
    dom.myScore.innerHTML = String(score)
  }

  let startTime = null
  let myEfficiencyTimer = null
  dom.wait.onclick = () => {
    dom.player1.classList.add('ready')
    if (mode === 'multi' && gameSocket) {
      // 准备完毕
      gameSocket.emit('ready', true)
    } else {
      // 单人模式直接开始
      start()
    }
  }

  function start () {
    // TODO 倒计时动画
    window.setTimeout(function () {
      dom.player1.classList.add('run')
      dom.player1.classList.remove('ready')
      startTime = Date.now()
      mySnake = new Snake({
        ctx: myCtx,
        x: 275,
        y: 180,
        length: 80,
        width: 6,
        color: '#000'
      })
      mySnake.food.update()
      if (mode === 'multi' && gameSocket) {
        hisSnake.food.update()
      }
      animate()
      myEfficiencyTimer = window.setInterval(() => {
        updateDOM()
      }, 1000)
    }, 2000)
  }

  let mode = 'single'

  function setGameMode (_mode) {
    if (_mode !== mode) {
      dom.wrapper.classList.remove(mode)
      dom.wrapper.classList.add(_mode)
      mode = _mode
      if (_mode === 'single') {
        // leaveRoom()
      }
      else if (_mode === 'multi') {
        // joinRoom()
        // hisSnake = new Snake({
        //   ctx: hisCtx,
        //   x: 275,
        //   y: 180,
        //   length: 80,
        //   width: 6,
        //   color: '#000'
        // })
      }
    }
  }

  function joinRoom () {
    api.logout().then(
      () => {},
      async () => {
        const room = prompt('请输入你要进入的房间：')
        const res = await api.auth(room)

        const userToken = res.userToken
        gameSocket = io(
          `//${window.location.hostname}/game?userToken=${encodeURIComponent(userToken)}`,
          {path: '/game'})

        // 接收系统消息
        gameSocket.on('sys', function (data) {
          response.innerHTML += data + '<br/>'
        })

        // 房间全部准备完毕
        gameSocket.on('all ready', function (data) {
          start()
        })

        // 接收对方操作
        gameSocket.on('his turn', function (hisTurn) {
          console.log(hisTurn)
          hisSnake.turn(hisTurn)
        })

        btnSend.onclick = e => {
          console.log(inputSend.value)
          gameSocket.emit('say', inputSend.value)
        }
      }
    )
  }

  function leaveRoom () {
    if (gameSocket) {
      gameSocket.emit('leave')
      gameSocket.close()
    }
  }

  const inputSend = document.getElementById('input-send')
  const btnSend = document.getElementById('btn-send')
  const response = document.getElementById('response')

  const radios = document.getElementsByName('mode')
  document.getElementById('mode-select').onclick = e => {
    let _mode = mode
    for (let i = 0; i < radios.length; i++) {
      if (radios[i].checked) {
        _mode = radios[i].value
        break
      }
    }
    setGameMode(_mode)
  }

})
